package com.micro.platform.user.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableList;
import com.micro.platform.starter.utils.AssertUtil;
import com.micro.platform.starter.utils.BeanCopyUtil;
import com.micro.platform.starter.utils.OptionalPlus;
import com.micro.platform.user.controller.menu.MenuInfosResp;
import com.micro.platform.user.controller.menu.MenuSyncRequ;
import com.micro.platform.user.entity.*;
import com.micro.platform.user.mapper.MenuMapper;
import com.micro.platform.user.service.*;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户-菜单表 服务实现类
 * </p>
 *
 * @author zhouyk
 * @since 2021-12-06
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private FunctionService functionService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RoleFunctionService roleFunctionService;
    @Autowired
    private FunctionApiService functionApiService;

    @Override
    public List<MenuInfosResp> listAll(String platformCode) {
        QueryWrapper<Menu> menuQueryWrapper = new QueryWrapper<>();
        menuQueryWrapper.lambda().eq(Menu::getDeleted, Boolean.FALSE).eq(Menu::getPlatformCode, platformCode);
        List<Menu> list = this.list(menuQueryWrapper);
        List<MenuInfosResp> listMenus = BeanCopyUtil.copyListProperties(list, MenuInfosResp::new);
        if (listMenus == null) {
            return Collections.emptyList();
        }
        return listMenus;
    }

    @Override
    public List<Menu> list(String platformCode) {
        QueryWrapper<Menu> menuQueryWrapper = new QueryWrapper<>();
        menuQueryWrapper.lambda().eq(Menu::getDeleted, Boolean.FALSE).eq(Menu::getPlatformCode, platformCode);
        List<Menu> list = this.list(menuQueryWrapper);
        return list;
    }

    @Override
    public List<MenuInfosResp> tree(String platformCode) {
        List<MenuInfosResp> menuInfosResps = listAll(platformCode);
        return buildTree(menuInfosResps);
    }

    @Override
    @Transactional
    public Boolean syncMenuInfos(List<MenuSyncRequ> menuSyncRequs, String platformCode) {
        if(CollectionUtils.isEmpty(menuSyncRequs)){
            UpdateWrapper<Menu> menuUpdateWrapper = new UpdateWrapper<>();
            menuUpdateWrapper
                    .lambda()
                    .eq(Menu::getPlatformCode, platformCode)
                    .set(Menu::getDeleted, Boolean.TRUE);
            return this.update(menuUpdateWrapper);
        }
        List<MenuSyncRequ> menuInfos = buildList(menuSyncRequs);
        List<Menu> menus = BeanCopyUtil.copyListProperties(menuInfos, Menu::new);
        if (CollectionUtils.isEmpty(menus)) {
            return Boolean.FALSE;
        }
        menus.stream().forEach(menu -> menu.setPlatformCode(platformCode));
        // 计算更新和新增
        List<Menu> list = this.list(platformCode);
        List<Menu> menusUpdates = new ArrayList<>();
        List<Menu> menusAdds = new ArrayList<>();
        Map<Long, Menu> mapId2Menu = list.stream().collect(Collectors.toMap(Menu::getId, java.util.function.Function.identity()));
        for (Menu menu : menus) {
            Long id = menu.getId();
            Menu menuUpdate = mapId2Menu.get(id);
            if (menuUpdate != null) {
                menusUpdates.add(mergeUpdateMenu(menuUpdate, menu));
            } else {
                menusAdds.add(menu);
            }
        }

        // 计算删除的ID
        Set<Long> dbIds = mapId2Menu.keySet();
        Set<Long> ids = menus.stream().map(Menu::getId).filter(Objects::nonNull).collect(Collectors.toSet());
        dbIds.removeAll(ids);

        boolean result = true;
        // 软删
        if (dbIds.size() > 0) {
            UpdateWrapper<Menu> menuUpdateWrapper = new UpdateWrapper<>();
            menuUpdateWrapper
                    .lambda()
                    .in(Menu::getId, dbIds)
                    .eq(Menu::getPlatformCode, platformCode)
                    .set(Menu::getDeleted, Boolean.TRUE);
            result = this.update(menuUpdateWrapper);
        }

        // 新增
        if (menusAdds.size() > 0) {
            result = result && this.saveBatch(menusAdds);
        }

        // 更新
        if (menusUpdates.size() > 0) {
            result = result && this.updateBatchById(menusUpdates);
        }

        AssertUtil.isTrue(result, "同步失败");
        return Boolean.TRUE;
    }

    private Menu mergeUpdateMenu(Menu menuUpdate, Menu menu) {
        menuUpdate.setSort(menu.getSort());
        menuUpdate.setPIndex(menu.getPIndex());
        menuUpdate.setTitle(menu.getTitle());
        menuUpdate.setTreeIndex(menu.getTreeIndex());
        menuUpdate.setRouteKey(menu.getRouteKey());
        menuUpdate.setDeleted(Boolean.FALSE);
        menuUpdate.setPlatformCode(menu.getPlatformCode());
        menuUpdate.setMeta(menu.getMeta());
        return menuUpdate;
    }

    @Override
    public List<Menu> getMenuTreeNodesById(Long menuId, String platformCode) {
        List<Menu> list = this.list(platformCode);
        if (null == menuId) {
            return list;
        }
        Map<Long, Menu> mapId2Menu = list.stream().collect(Collectors.toMap(Menu::getId, java.util.function.Function.identity()));
        Map<Long, List<Menu>> mapPIndex2Menus = list.stream().filter(menu -> null != menu.getPIndex()).collect(Collectors.groupingBy(Menu::getPIndex, Collectors.toList()));
        List<Menu> menus = new ArrayList<>();
        Menu menu = mapId2Menu.get(menuId);
        if (menu == null) {
            return menus;
        }
        menus.add(menu);
        Long pIndex = menu.getTreeIndex();
        menus.addAll(getChildrensNodes(pIndex, mapPIndex2Menus));
        return menus;
    }

    private List<Menu> getChildrensNodes(Long pIndex, Map<Long, List<Menu>> mapPIndex2Menus) {
        List<Menu> menus = mapPIndex2Menus.get(pIndex);
        if (CollectionUtils.isEmpty(menus)) {
            return Collections.EMPTY_LIST;
        } else {
            List<Long> collect = menus.stream().map(Menu::getTreeIndex).collect(Collectors.toList());
            for (Long pIndexT : collect) {
                menus.addAll(getChildrensNodes(pIndexT, mapPIndex2Menus));
            }
        }
        return menus;
    }

    private List<MenuInfosResp> buildTree(List<MenuInfosResp> menuInfo) {
        Map<Long, MenuInfosResp> mapTreeIndex2MenuInfosResp = menuInfo.stream().collect(Collectors.toMap(MenuInfosResp::getTreeIndex, java.util.function.Function.identity()));
        ArrayList<MenuInfosResp> menuInfosResps = new ArrayList<>();
        menuInfosResps.addAll(menuInfo.stream().filter(menu -> null == menu.getPIndex()).collect(Collectors.toList()));
        // 没有找到对应的PID，就当作根节点处理
        for (MenuInfosResp menu : menuInfo) {
            if (!mapTreeIndex2MenuInfosResp.containsKey(menu.getPIndex()) && !menuInfosResps.contains(menu)) {
                menuInfosResps.add(menu);
            }
        }
        menuInfo.removeAll(menuInfosResps);
        Map<Long, List<MenuInfosResp>> mapPindex2Childrens = menuInfo.stream().collect(Collectors.groupingBy(MenuInfosResp::getPIndex, Collectors.toList()));
        for (MenuInfosResp menuInfosResp : menuInfosResps) {
            buildTreeChildrens(menuInfosResp, mapPindex2Childrens);
        }
        return menuInfosResps.stream().sorted(Comparator.comparing(MenuInfosResp::getSort)).collect(Collectors.toList());
    }

    private void buildTreeChildrens(MenuInfosResp menuInfosResp, Map<Long, List<MenuInfosResp>> mapPindex2Childrens) {
        Long treeIndex = menuInfosResp.getTreeIndex();
        List<MenuInfosResp> menuInfosResps = mapPindex2Childrens.get(treeIndex);
        if (CollectionUtils.isEmpty(menuInfosResps)) {
            return;
        }
        menuInfosResp.setChildrens(menuInfosResps.stream().sorted(Comparator.comparing(MenuInfosResp::getSort)).collect(Collectors.toList()));
        for (MenuInfosResp infosResp : menuInfosResps) {
            buildTreeChildrens(infosResp, mapPindex2Childrens);
        }
    }

    private List<MenuSyncRequ> buildList(List<MenuSyncRequ> menuSyncRequs) {
        List<MenuSyncRequ> menuSyncs = new ArrayList<>();
        Long nextIndex = 0L;
        for (int i = 0; i < menuSyncRequs.size(); i++) {
            nextIndex++;
            nextIndex = buildListChildrens(menuSyncs, menuSyncRequs.get(i), nextIndex, null, i);
        }
        return menuSyncs;
    }

    private Long buildListChildrens(List<MenuSyncRequ> menuSync, MenuSyncRequ menuSyncRequs, Long index, Long pIndex, Integer sort) {
        List<MenuSyncRequ> childrens = menuSyncRequs.getChildrens();
        menuSyncRequs.setChildrens(null);
        menuSyncRequs.setTreeIndex(index);
        menuSyncRequs.setSort(sort);
        menuSyncRequs.setPIndex(pIndex);
        menuSync.add(menuSyncRequs);
        Long id = index;
        if (CollectionUtils.isNotEmpty(childrens)) {
            for (int i = 0; i < childrens.size(); i++) {
                index++;
                index = buildListChildrens(menuSync, childrens.get(i), index, id, i);
            }
        }
        return index;
    }

    @Override
    public List<MenuInfosResp> treeByUserId(Long userId, String platformCode) {
        if (userId == null) {
            return Collections.EMPTY_LIST;
        }
        List<UserRole> userRoles = userRoleService.getUserRolesByUserIds(ImmutableList.of(userId));
        List<Long> roleIds = userRoles.stream().map(UserRole::getRoleId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(roleIds)) {
            return Collections.EMPTY_LIST;
        }
        List<RoleFunction> roleFunctions = roleFunctionService.getRoleFunctionsByRoleIds(roleIds);
        List<Long> functionIds = roleFunctions.stream().map(RoleFunction::getFunctionId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(functionIds)) {
            return Collections.EMPTY_LIST;
        }
        List<Function> functions = functionService.listByIds(functionIds, platformCode);
        List<Long> menuIds = functions.stream().map(Function::getMenuId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(menuIds)) {
            return Collections.EMPTY_LIST;
        }
        Map<Long, Long> mapFunctionId2MenuId = functions.stream()
                .filter(function -> Optional
                        .ofNullable(function)
                        .filter(value -> Objects.nonNull(value.getMenuId()))
                        .orElse(null) != null)
                .collect(Collectors.toMap(Function::getId, Function::getMenuId, (A, B) -> A));

        List<FunctionApi> functionApisByFunctionIds = functionApiService.getFunctionApisByFunctionIds(functionIds);
        Map<Long, Set<String>> mapMenuId2ApiUrls = functionApisByFunctionIds.stream()
                .filter(functionApi -> Optional
                        .ofNullable(mapFunctionId2MenuId.get(functionApi.getFunctionId()))
                        .orElse(null) != null
                )
                .collect(Collectors.groupingBy(functionApi -> mapFunctionId2MenuId.get(functionApi.getFunctionId()), Collectors.mapping(FunctionApi::getApiUrl, Collectors.toSet())));

        List<MenuInfosResp> menuInfosResps = listAll(platformCode);
        menuInfosResps.stream().forEach(menuInfosResp -> OptionalPlus.ofNullable(mapMenuId2ApiUrls.get(menuInfosResp.getId())).filter(CollectionUtils::isNotEmpty).doNotNull(value -> menuInfosResp.setApiUrls(new ArrayList<>(value))));
        List<MenuInfosResp> menuInfos = getParentMenus(menuIds, menuInfosResps);
        List<MenuInfosResp> values = new ArrayList<>(menuInfos.stream().collect(Collectors.toMap(MenuInfosResp::getId, A -> A, (A, B) -> A)).values());
        return buildTree(values);
    }

    private List<MenuInfosResp> getParentMenus(List<Long> menuIds, List<MenuInfosResp> menuInfosResps) {
        List<MenuInfosResp> menuInfos = menuInfosResps.stream().filter(menuInfosResp -> menuIds.contains(menuInfosResp.getId())).collect(Collectors.toList());
        List<Long> pIndexs = menuInfos.stream().map(MenuInfosResp::getPIndex).collect(Collectors.toList());
        List<MenuInfosResp> parentMenus = menuInfosResps.stream().filter(menuInfosResp -> pIndexs.contains(menuInfosResp.getTreeIndex())).collect(Collectors.toList());
        List<Long> pIds = parentMenus.stream().map(MenuInfosResp::getId).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(pIds)) {
            menuInfos.addAll(getParentMenus(pIds, menuInfosResps));
        }
        return menuInfos;
    }

}
